#ifndef HERM_SOCKET_SOCKET_BASE_H
#define HERM_SOCKET_SOCKET_BASE_H

#include <string>
#include "herm/export_symbols.h"
#include "herm/base/utils/exception.h"
#include "herm/base/socket/socket_def.h"
#include "herm/base/socket/address.h"

#define HERM_SOCKET_EXCEPTION(type) \
	herm::SocketException(__FILE__, __LINE__, __FUNCTION__, type, herm::Private::GetLastError())

#define HERM_SOCKET_DESC_EXCEPTION(desc) \
	herm::SocketException(__FILE__, __LINE__, __FUNCTION__, 0, desc)
 
namespace herm{

void HERM_API Init();
void HERM_API UnInit();

class HERM_API SocketException : public Exception
{
public:
	SocketException(const std::string& file, int line, const std::string& func, int type, int code);
    SocketException(const std::string& file, int line, const std::string& func, int type, const std::string& desc);
}; 

namespace Private{
struct HERM_API StreamType
{
	static int Family();
	static int SocketType();
	static int Protocol();
};

struct HERM_API DatagramType
{
	static int Family();
	static int SocketType();
	static int Protocol();
};
}

class HERM_API SocketBase
{
protected:
	SocketBase(void);
	~SocketBase(void);
public:
	/**
	* Retrieves socket handle.
	* @return: socket handle.
	*/
	inline Handle GetHandle() const
	{
		return m_socket;
	}

	inline void Attach(Handle s)
	{
		m_socket = s;
	}

	inline Handle Detach()
	{
		Handle h = m_socket;
		m_socket = INVALID_HANDLE;

		return h;
	}

	void SetBlock();
	void SetNonblock();

	int GetSendBufSize() const;
	void SetSendBufSize(int size);

	int GetRecvBufSize() const;
	void SetRecvBufSize(int size);

	bool GetReuseAddr() const;
	void SetReuseAddr(bool opt = true);

	bool GetLocalAddress(Address& addr, bool isDatagram = true) const;
	void GetRemoteAddress(Address& addr) const;
protected:
	inline bool IsBlock() const
	{
		return m_block;
	}
private:
	int GetSockOpt(int level, int optName) const;
	void SetSockOpt(int level, int optName, int optVal, SockLen valLen);
private:
	Handle m_socket;
	bool m_block;
};

template <class SocketTypeT>
class BasicSocket : public SocketBase
{
public:
	typedef SocketTypeT SocketType;

	BasicSocket(void)
	{
		Attach(INVALID_HANDLE);
	}

	~BasicSocket(void)
	{
		Close();
	}

// Declare the copy constructor and assignment operator in private, so
// ensure the BasicSocket object not to allow copy!
private:
	BasicSocket(const BasicSocket&);
	const BasicSocket& operator=(const BasicSocket&);
public:
	void Open(bool isReuseAddr = false)
	{
		Handle sock = ::socket(SocketType::Family(), SocketType::SocketType(), SocketType::Protocol());
		if (sock == INVALID_HANDLE)
			throw HERM_SOCKET_EXCEPTION(ST_OTHER);

		Attach(sock);
		if (isReuseAddr)
			SetReuseAddr();
	}

	void Open(const Address& addr, bool isReuseAddr = false)
	{
		Open(isReuseAddr);
		Bind(addr);
	}

	void Bind(const Address& addr)
	{
		if (::bind(GetHandle(), (sockaddr*)(&addr.SockAddr()), sizeof(sockaddr_in)) == Private::FAILED)
			throw HERM_SOCKET_EXCEPTION(ST_OTHER);
	}

	/**
	* Destory socket object.
	*/
	void Close()
	{
		herm::Handle sock = GetHandle();
		if (sock == INVALID_HANDLE)
			return;
	
		Private::Close(sock);
		Attach(INVALID_HANDLE);
	}
};
}

#endif
